<?php

namespace App\Core;

use App\Core\Exception\DefaultException;
use App\Util\CommonHelper;
use EasySwoole\Component\Singleton;
use EasySwoole\Validate\Validate;
use ReflectionClass;

/**
 * 表单基类，可以用于表单提交的处理，包括校验，读取，写入等操作
 *
 * @property array $attributeData
 */
class BaseForm extends BaseCore{
    
    use Singleton;
    
    private $_attributes;

    const PHONE_REG = '/^((13[0-9])|(14[5,7,9])|(15[^4])|(18[0-9])|(17[0,1,3,5,6,7,8]))\\d{8}$/';//手机号码正则规则
    
    /**
     * @param array $data
     */
    public function __construct(array $data){
        $this->setAttributes($data);
    }
    
    /**
     * Returns the list of attribute names.
     * By default, this method returns all public non-static properties of the class.
     * You may override this method to change the default behavior.
     * @return array list of attribute names.
     */
    public function attributes(){
        $class = new ReflectionClass($this);
        $names = [];;
        
        foreach($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property){
            if(!$property->isStatic()){
                $names[] = $property->getName();
            }
        }
        
        return $names;
    }
    
    /**
     * 校验规则
     * @return array
     * @var array $rules
     * <code>
     * [
     *      field=>['rules','字段名称','错误信息']
     * ]
     * </code>
     * 其中字段名称，错误信息可以不填写，如果需要填写则一定要按照顺序填写
     */
    public function rules(){
        return [];
    }
    
    /**
     * 创建校验器
     * @param array $messages
     * @return Validate
     * @throws \EasySwoole\Validate\Exception\Runtime
     */
    public function createValidator($messages = [],$rules_method=false){
        $rules = $alias = [];
        $ruls_data = $rules_method ? $this->$rules_method() : $this->rules();
        foreach($ruls_data as $field => $item){
            is_array($item) or $item = [$item];
            $rules[$field] = $item[0];
            if(isset($item[1])){
                $alias[$field] = $item[1];
            }
            if(isset($item[2])){
                $messages[$field] = $item[2];
            }
        }
        
        return Validate::make($rules, $messages, $alias);
    }
    
    /**
     * Returns attribute values.
     * @param array $names list of attributes whose value needs to be returned.
     * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
     * If it is an array, only the attributes in the array will be returned.
     * @param array $except list of attributes whose value should NOT be returned.
     * @return array attribute values (name => value).
     */
    public function getAttributes($names = null, $except = []){
        $values = [];
        if($names === null){
            $names = $this->attributes();
        }
        foreach($names as $name){
            $values[$name] = $this->$name;
        }
        foreach($except as $name){
            unset($values[$name]);
        }
        
        return $values;
    }
    
    /**
     * Sets the attribute values in a massive way.
     * @param array $values attribute values (name => value) to be assigned to the model.
     * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
     * @see safeAttributes()
     * @see attributes()
     */
    public function setAttributes($values){
        if(is_array($values)){
            $attributes = array_flip($this->attributes());
            foreach($values as $name => $value){
                if(isset($attributes[$name])){
                    $this->$name = $value;
                }
            }
        }
        return $this;
    }
    
    /**
     * 获取对象公共的值，自动过滤=null的键
     * @param array $except 过滤不取的key
     * @return array
     */
    public function getAttributeData(array $except = []){
        $attributes = $this->getAttributes();
        foreach($attributes as $k => $v){
            if($v === null || in_array($k, $except)){
                unset($attributes[$k]);
                continue;
            }
            if(is_string($v)){
                //去空格处理
                $attributes[$k] = trim($v);
            }
        }
        return $attributes;
    }
    
    /**
     * Returns the value of a component property.
     *
     * This method will check in the following order and act accordingly:
     *
     *  - a property defined by a getter: return the getter result
     *  - a property of a behavior: return the behavior property value
     *
     * Do not call this method directly as it is a PHP magic method that
     * will be implicitly called when executing `$value = $component->property;`.
     * @param $name
     * @return mixed
     * @throws DefaultException
     * @see __set()
     */
    public function __get($name){
        $getter = 'get' . $name;
        if(method_exists($this, $getter)){
            // read property, e.g. getName()
            return $this->$getter();
        }
        
        if(method_exists($this, 'set' . $name)){
            throw new DefaultException('Getting write-only property: ' . get_class($this) . '::' . $name);
        }
        
        throw new DefaultException('Getting unknown property: ' . get_class($this) . '::' . $name);
    }
    
    /**
     * Sets the value of a component property.
     *
     * This method will check in the following order and act accordingly:
     *
     *  - a property defined by a setter: set the property value
     *  - an event in the format of "on xyz": attach the handler to the event "xyz"
     *  - a behavior in the format of "as xyz": attach the behavior named as "xyz"
     *  - a property of a behavior: set the behavior property value
     *
     * Do not call this method directly as it is a PHP magic method that
     * will be implicitly called when executing `$component->property = $value;`.
     * @param $name
     * @param $value
     * @throws DefaultException
     * @see __get()
     */
    public function __set($name, $value){
        $setter = 'set' . $name;
        if(method_exists($this, $setter)){
            // set property
            $this->$setter($value);
            
            return;
        }
        
        if(method_exists($this, 'get' . $name)){
            throw new DefaultException('Setting read-only property: ' . get_class($this) . '::' . $name);
        }
        
        throw new DefaultException('Setting unknown property: ' . get_class($this) . '::' . $name);
    }
    
    /**
     * Checks if a property is set, i.e. defined and not null.
     *
     * This method will check in the following order and act accordingly:
     *
     *  - a property defined by a setter: return whether the property is set
     *  - a property of a behavior: return whether the property is set
     *  - return `false` for non existing properties
     *
     * Do not call this method directly as it is a PHP magic method that
     * will be implicitly called when executing `isset($component->property)`.
     * @param string $name the property name or the event name
     * @return bool whether the named property is set
     * @see https://secure.php.net/manual/en/function.isset.php
     */
    public function __isset($name){
        $getter = 'get' . $name;
        if(method_exists($this, $getter)){
            return $this->$getter() !== null;
        }
        
        return false;
    }
    
    /**
     * Sets a component property to be null.
     *
     * This method will check in the following order and act accordingly:
     *
     *  - a property defined by a setter: set the property value to be null
     *  - a property of a behavior: set the property value to be null
     *
     * Do not call this method directly as it is a PHP magic method that
     * will be implicitly called when executing `unset($component->property)`.
     * @param $name
     * @throws DefaultException
     * @see https://secure.php.net/manual/en/function.unset.php
     */
    public function __unset($name){
        $setter = 'set' . $name;
        if(method_exists($this, $setter)){
            $this->$setter(null);
            return;
        }
        
        throw new DefaultException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name);
    }
}